﻿// Copyright 2012 YarUnderoaker
// This macros is released to the public as open-source under Mozilla Public Licence.

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;

using System.Linq;
using System.Text;
using System.Windows.Media;
using System.Windows.Forms;
using System.Windows.Media.Imaging;
using System.Data;
using System.Xml;
using System.Xml.Linq;

using System.Diagnostics;
using System.IO;

using Autodesk;
using Autodesk.Revit;
using Autodesk.Revit.DB;
using Autodesk.Revit.UI;
using Autodesk.Revit.DB.Structure;
using Autodesk.Revit.ApplicationServices;
using Autodesk.Revit.UI.Events;

namespace RoomFinishesAddIn
{

    public class OverwriteFamilyLoadOptions : IFamilyLoadOptions
    {
        public bool OnFamilyFound(bool familyInUse, out bool overwriteParameterValues)
        {
            overwriteParameterValues = true;
            return true;
        }


        public bool OnSharedFamilyFound(Family sharedFamily, bool familyInUse, out FamilySource source, out bool overwriteParameterValues)
        {
            source = FamilySource.Family;
            overwriteParameterValues = true;
            return true;
        }
    }

    public class OverlapWarningSwallower : IFailuresPreprocessor
    {
        public List<ICollection<ElementId>> Neightbours = new List<ICollection<ElementId>>();

        public FailureProcessingResult PreprocessFailures(FailuresAccessor a)
        {
            // inside event handler, get all warnings

            IList<FailureMessageAccessor> failures = a.GetFailureMessages();

            foreach (FailureMessageAccessor f in failures)
            {
                // check failure definition ids 
                // against ones to dismiss:

                FailureDefinitionId id = f.GetFailureDefinitionId();

                if (BuiltInFailures.OverlapFailures.WallsOverlap == id)
                {
                    Neightbours.Add(f.GetFailingElementIds());
                    a.DeleteWarning(f);
                }
            }
            return FailureProcessingResult.Continue;
        }
    }

    [Autodesk.Revit.Attributes.Transaction(Autodesk.Revit.Attributes.TransactionMode.Manual)]
    [Autodesk.Revit.Attributes.Regeneration(Autodesk.Revit.Attributes.RegenerationOption.Manual)]
    [Autodesk.Revit.Attributes.Journaling(Autodesk.Revit.Attributes.JournalingMode.NoCommandData)]
    public class StartupClass : IExternalApplication
    {
        // ExternalCommands assembly path
        static string AddInPath = System.Reflection.Assembly.GetExecutingAssembly().Location; //typeof(StartupClass).Assembly.Location;
        // Button icons directory
        static string ButtonIconsFolder = Path.GetDirectoryName(AddInPath);
        static string XmlConfig = Environment.GetEnvironmentVariable("LOCALAPPDATA") + "\\Autodesk\\Revit\\";

        public static UIControlledApplication uiRevit = null;

        public static int currentRoomIndex = 0;
        public static Autodesk.Revit.UI.TextBox roomNumberBox = null;

        private const string optionsFileName = "FinishingAddOnOptions.xml";

        public static bool groupingByMaterial = true;
        public static int firstColumnValue = 0;
        public static int takeoffLanguage = 0;

        private void LoadOptioins()
        {
            string fullPath = Path.Combine(XmlConfig, optionsFileName);
            if (File.Exists(fullPath))
            {
                StreamReader reader = new StreamReader(fullPath);
                XDocument xmlDoc = XDocument.Load(new XmlTextReader(reader));
                reader.Close();

                XElement element = xmlDoc.Element("Options");
                XAttribute a1 = element.Attribute("GroupingByMaterial");
                XAttribute a2 = element.Attribute("FirstColumnValue");
                XAttribute a3 = element.Attribute("TakeoffLanguage");

                groupingByMaterial = int.Parse(a1.Value) == 0;
                firstColumnValue = int.Parse(a2.Value);
                takeoffLanguage = int.Parse(a3.Value);
            }
        }

        private void SaveOptioins()
        {
            string fullPath = Path.Combine(XmlConfig, optionsFileName);
            StreamWriter writer = new StreamWriter(fullPath);
            XDocument xmlDoc = new XDocument(
                new XElement("Options",
                    new XAttribute("GroupingByMaterial", groupingByMaterial ? "0" : "1"),
                    new XAttribute("FirstColumnValue", firstColumnValue.ToString()),
                    new XAttribute("TakeoffLanguage", takeoffLanguage.ToString())
                    )
                );
            xmlDoc.Save(new XmlTextWriter(writer));
            writer.Close();
        }

        public Autodesk.Revit.UI.Result OnStartup(UIControlledApplication ctrlApp)
        {
            try
            {
                // create customer Ribbon Items
                uiRevit = ctrlApp;
                CreateRibbonSamplePanel();
                LoadOptioins();
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.ToString(), "RoomFinishes Ribbon");

                return Autodesk.Revit.UI.Result.Failed;
            }

            return Autodesk.Revit.UI.Result.Succeeded;

        }

        public Autodesk.Revit.UI.Result OnShutdown(UIControlledApplication ctrlApp)
        {
            try
            {
                // save options to external file
                SaveOptioins();
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.ToString(), "RoomFinishes Ribbon");

                return Autodesk.Revit.UI.Result.Failed;
            }
            return Autodesk.Revit.UI.Result.Succeeded;
        }


        private static List<T> RawConvertSetToList<T>(IEnumerable set)
        {
            List<T> list = (from T p in set select p).ToList<T>();
            return list;
        }

        private static Definition RawCreateSharedParameter(UIApplication app, string name, string group, ParameterType type, bool visible)
        {
            DefinitionFile файлОбщихПараметров = app.Application.OpenSharedParameterFile();
            if (файлОбщихПараметров == null) throw new Exception("Файл общих параметров отсутствует!");

            DefinitionGroup dg = RawConvertSetToList<DefinitionGroup>(файлОбщихПараметров.Groups).FirstOrDefault(g => g.Name == group);
            if (dg == null) dg = файлОбщихПараметров.Groups.Create(group);

            Definition def = RawConvertSetToList<Definition>(dg.Definitions).FirstOrDefault(d => d.Name == name);
            if (def != null) return def;
            def = dg.Definitions.Create(name, type, visible);

            return def;
        }

        public static bool HasSharedParameter(UIApplication app, string name, string group, ParameterType type)
        {
            DefinitionFile файлОбщихПараметров = app.Application.OpenSharedParameterFile();
            if (файлОбщихПараметров == null) throw new Exception("Файл общих параметров отсутствует!");

            DefinitionGroup dg = RawConvertSetToList<DefinitionGroup>(файлОбщихПараметров.Groups).FirstOrDefault(g => g.Name == group);
            if (dg == null) return false;
            if (dg.Name != group) return false;

            Definition def = RawConvertSetToList<Definition>(dg.Definitions).FirstOrDefault(d => d.Name == name);
            if (def == null) return false;
            if (def.Name == name && def.ParameterType == type) return true;
            return false;
        }

        public static Parameter GetOrCreateParameter(UIApplication app, Element element, string paramName, ParameterType paramType, bool visible)
        {
            Parameter параметер = element.get_Parameter(paramName);
            if (параметер == null)
            {
                Definition новоеОпределение = RawCreateSharedParameter(app, paramName, "YarAddOnGroup", paramType, visible);
                Category категория = app.ActiveUIDocument.Document.Settings.Categories.get_Item((BuiltInCategory)element.Category.Id.IntegerValue);

                BindingMap карта = app.ActiveUIDocument.Document.ParameterBindings;
                if (карта.Contains(новоеОпределение))
                {
                    Autodesk.Revit.DB.InstanceBinding b = карта.get_Item(новоеОпределение) as InstanceBinding;
                    b.Categories.Insert(категория);
                    карта.ReInsert(новоеОпределение, b, BuiltInParameterGroup.PG_IDENTITY_DATA);
                }
                else
                {
                    CategorySet наборКатегорий = app.Application.Create.NewCategorySet();
                    наборКатегорий.Insert(категория);
                    Autodesk.Revit.DB.InstanceBinding привязка = app.ActiveUIDocument.Document.Application.Create.NewInstanceBinding(наборКатегорий);
                    карта.Insert(новоеОпределение, привязка, BuiltInParameterGroup.PG_IDENTITY_DATA);
                }
                параметер = element.get_Parameter(paramName);
                if (параметер == null) throw new Exception("Невозможно создать новый общий параметр - " + paramName);
            }
            return параметер;
        }

        public static void CreateInstanceParameter(UIApplication app, string paramName, BuiltInCategory category, ParameterType paramType, bool visible)
        {
            if (!HasSharedParameter(app, paramName, "YarAddOnGroup", paramType))
            {
                Definition новоеОпределение = RawCreateSharedParameter(app, paramName, "YarAddOnGroup", paramType, visible);
                CategorySet наборКатегорий = app.Application.Create.NewCategorySet();
                Category категория = app.ActiveUIDocument.Document.Settings.Categories.get_Item(category);

                наборКатегорий.Insert(категория);

                Autodesk.Revit.DB.Binding привязка = app.ActiveUIDocument.Document.Application.Create.NewInstanceBinding(наборКатегорий);
                //Autodesk.Revit.DB.Binding привязка = this.Document.Application.Create.NewTypeBinding(наборКатегорий);
                BindingMap карта = app.ActiveUIDocument.Document.ParameterBindings;
                карта.Insert(новоеОпределение, привязка, BuiltInParameterGroup.PG_IDENTITY_DATA);
            }
        }

        private static int CompareRoomsByNumber(Element x, Element y)
        {
            Autodesk.Revit.DB.Architecture.Room xRoom = x as Autodesk.Revit.DB.Architecture.Room;
            Autodesk.Revit.DB.Architecture.Room yRoom = y as Autodesk.Revit.DB.Architecture.Room;
            Autodesk.Revit.DB.Document doc = xRoom.Document;
            Parameter xNum = xRoom.get_Parameter(BuiltInParameter.ROOM_NUMBER);
            Parameter yNum = yRoom.get_Parameter(BuiltInParameter.ROOM_NUMBER);
            return xNum.AsString().CompareTo(yNum.AsString());
        }

        public static IList<Element> PlacedRoomFilter(Autodesk.Revit.DB.Document doc)
        {
            List<Element> m_rooms = new List<Element>();
            FilteredElementIterator elementIterator = (new FilteredElementCollector(doc)).WherePasses(new Autodesk.Revit.DB.Architecture.RoomFilter()).GetElementIterator();
            elementIterator.Reset();

            Parameter viewPhaseParam = doc.ActiveView.get_Parameter(BuiltInParameter.VIEW_PHASE);
            ElementId phaseId = viewPhaseParam.AsElementId();

            // try to find all the rooms and room tags in the project and add to the list
            while (elementIterator.MoveNext())
            {
                object obj = elementIterator.Current;

                // find the rooms, skip those rooms which don't locate at Level yet.
                Autodesk.Revit.DB.Architecture.Room tmpRoom = obj as Autodesk.Revit.DB.Architecture.Room;
                
                if (null != tmpRoom && ElementId.InvalidElementId != tmpRoom.LevelId)
                {
                    Parameter roomPhaseParam = tmpRoom.get_Parameter(BuiltInParameter.ROOM_PHASE);
                    ElementId roomPhaseId = roomPhaseParam.AsElementId();
                    if (roomPhaseId == phaseId)
                    {
                        m_rooms.Add(tmpRoom);
                        continue;
                    }
                }
            }

            m_rooms.Sort(CompareRoomsByNumber);
            return m_rooms;
        }

        private void CreateRibbonSamplePanel()
        {
            string tabName = "Отделка";
            uiRevit.CreateRibbonTab(tabName);
            // create a Ribbon panel which contains three stackable buttons and one single push button.
            string panelName = "Иструменты работы с деталями стен для внутренней отделки";
            RibbonPanel ribbonPanel = uiRevit.CreateRibbonPanel(tabName, panelName);

            PushButtonData pushButtonData1 = new PushButtonData("DeterminePartsOfRoom", "Определить детали \nотделки", AddInPath, "RoomFinishesAddIn.DeterminePartsOfRoom");
            PushButton button1 = ribbonPanel.AddItem(pushButtonData1) as PushButton;
            button1.LargeImage = new BitmapImage(new Uri(Path.Combine(ButtonIconsFolder, "DeterminePartsOfRoom.png"), UriKind.Absolute));
            button1.ToolTip = "Определение элементов отделки - деталей стен, отделочных стен, полов, потолков и кровель, которые примыкают к помещениям. Отделочные стены должны иметь значение 'Отделка' в параметре 'Группа модели'";

            PushButtonData pushButtonData2 = new PushButtonData("IsolatePreviousRoomParts", "Предыдущее помещение", AddInPath, "RoomFinishesAddIn.IsolatePreviousRoomParts");

            TextBoxData testBoxData = new TextBoxData("RoomSelector");

            PushButtonData pushButtonData3 = new PushButtonData("IsolateNextRoomParts", "Следующее помещение", AddInPath, "RoomFinishesAddIn.IsolateNextRoomParts");

            IList<RibbonItem> ribbonItemsStacked = ribbonPanel.AddStackedItems(pushButtonData2, testBoxData, pushButtonData3);
            ((PushButton)ribbonItemsStacked[0]).LargeImage = new BitmapImage(new Uri(Path.Combine(ButtonIconsFolder, "previous16.png"), UriKind.Absolute));
            ((PushButton)ribbonItemsStacked[0]).ToolTip = "Изолировать детали предыдущего помещения.";
            roomNumberBox = ((Autodesk.Revit.UI.TextBox)ribbonItemsStacked[1]);
            roomNumberBox.PromptText = "номер";
            roomNumberBox.EnterPressed += new EventHandler<TextBoxEnterPressedEventArgs>(OnRoomSelected);
            ((PushButton)ribbonItemsStacked[2]).LargeImage = new BitmapImage(new Uri(Path.Combine(ButtonIconsFolder, "next16.png"), UriKind.Absolute));
            ((PushButton)ribbonItemsStacked[2]).ToolTip = "Изолировать детали следующего помещения.";

            PushButtonData pushButtonData4 = new PushButtonData("HorizontalDivideParts", "Разделить детали\n рабочей плоскостью", AddInPath, "RoomFinishesAddIn.WorkplaneDivideParts");
            PushButton button2 = ribbonPanel.AddItem(pushButtonData4) as PushButton;
            button2.LargeImage = new BitmapImage(new Uri(Path.Combine(ButtonIconsFolder, "HorizDivideParts.png"), UriKind.Absolute));
            button2.ToolTip = "Делит детали отделки по линии пересечения с рабочей плоскостью.";
           
            PushButtonData pushButtonData6 = new PushButtonData("MassFromRoom", "Формообразующие\n по помещению", AddInPath, "RoomFinishesAddIn.CreateMassFromRoom");
            PushButton button4 = ribbonPanel.AddItem(pushButtonData6) as PushButton;
            button4.LargeImage = new BitmapImage(new Uri(Path.Combine(ButtonIconsFolder, "MassFromRoom.png"), UriKind.Absolute));
            button4.ToolTip = "Создает формообразующие по форме помещения.";

            PushButtonData pushButtonData7 = new PushButtonData("FinishingWall", "Отделочная\n стена", AddInPath, "RoomFinishesAddIn.CreateFinishingWall");
            PushButton button5 = ribbonPanel.AddItem(pushButtonData7) as PushButton;
            button5.LargeImage = new BitmapImage(new Uri(Path.Combine(ButtonIconsFolder, "FinishingWall.png"), UriKind.Absolute));
            button5.ToolTip = "Создает отделочные стены по периметру помещения."; 

            PushButtonData pushButtonData5 = new PushButtonData("CreateFinishingSchedule", "Ведомость отделки", AddInPath, "RoomFinishesAddIn.CreateFinishingSchedule");
            PushButton button3 = ribbonPanel.AddItem(pushButtonData5) as PushButton;
            button3.LargeImage = new BitmapImage(new Uri(Path.Combine(ButtonIconsFolder, "FinishingSchedule.png"), UriKind.Absolute));
            button3.ToolTip = "Создает статическую ведомость отделки.";

            PushButtonData pushButtonData9 = new PushButtonData("FinishingOptions", "Настройки", AddInPath, "RoomFinishesAddIn.ShowFinishingOptions");
            PushButton button6 = ribbonPanel.AddItem(pushButtonData9) as PushButton;
            button6.LargeImage = new BitmapImage(new Uri(Path.Combine(ButtonIconsFolder, "FinishingOptions.png"), UriKind.Absolute));
            button6.ToolTip = "Открывает диалог настроек дополнения."; 
        }

        public void OnRoomSelected(object sender, TextBoxEnterPressedEventArgs args)
        {
            Autodesk.Revit.UI.TextBox textbox = sender as Autodesk.Revit.UI.TextBox;
            if (null == textbox) { return; }
            if (textbox.Value == null || ((string)textbox.Value).Length == 0) { return; }

            Autodesk.Revit.DB.Document doc = args.Application.ActiveUIDocument.Document;
            Autodesk.Revit.DB.Transaction транзакция = new Autodesk.Revit.DB.Transaction(doc, "OnRoomSelected");
            транзакция.Start();

            try
            {
                IList<Element> коллекцияКомнат = StartupClass.PlacedRoomFilter(doc);

                int i = 0;
                foreach(Autodesk.Revit.DB.Architecture.Room room in коллекцияКомнат)
                {
                    if (string.Compare((string)textbox.Value, room.Number) == 0)
                    {
                        StartupClass.currentRoomIndex = i;
                        break;
                    }
                    i++;
                }
                
                Autodesk.Revit.DB.Architecture.Room currentRoom = коллекцияКомнат[StartupClass.currentRoomIndex] as Autodesk.Revit.DB.Architecture.Room;
                textbox.Value = currentRoom.Number;

                Parameter roomNumberParam = currentRoom.get_Parameter(BuiltInParameter.ROOM_NUMBER);
                string roomNumber = roomNumberParam.AsString();
                Parameter roomNameParam = currentRoom.get_Parameter(BuiltInParameter.ROOM_NAME);
                string roomName = roomNameParam.AsString();

                if (roomNumber.Count() > 0)
                {
                    FilteredElementCollector коллектор = new FilteredElementCollector(doc);
                    ElementCategoryFilter фильтр2 = new ElementCategoryFilter(BuiltInCategory.OST_Walls);
                    ICollection<Element> коллекцияСтен = коллектор.WherePasses(фильтр2).WhereElementIsNotElementType().ToElements();

                    List<ElementId> isolationers = new List<ElementId>();

                    foreach (Wall wall in коллекцияСтен)
                    {
                        if (PartUtils.HasAssociatedParts(doc, wall.Id))
                        {
                            // Добавляем детали стены смежные с помещением
                            List<ElementId> parts = new List<ElementId>();
                            parts.AddRange(PartUtils.GetAssociatedParts(doc, wall.Id, true, true));
                            foreach (ElementId id in parts)
                            {
                                Element part = doc.GetElement(id);
                                Parameter p = part.get_Parameter("Номер помещения");
                                if (p == null)
                                    throw new Exception("У деталей нет параметра 'Номер помещения', запустите команду 'Определить детали стен примыкающие к помещениям'");

                                if (string.Compare(roomNumber, p.AsString()) == 0) { isolationers.Add(id); }
                            }
                        } // has parts
                        else
                        {
                            // Добавляем стену полностью
                            Parameter p = wall.WallType.get_Parameter(BuiltInParameter.ALL_MODEL_MODEL);
                            if (string.Compare("Отделка", p.AsString()) == 0)
                            {
                                p = wall.get_Parameter("Номер помещения");
                                if (p == null)
                                    throw new Exception("У стены нет параметра 'Номер помещения', запустите команду 'Определить детали стен примыкающие к помещениям'");

                                if (string.Compare(roomNumber, p.AsString()) == 0) { isolationers.Add(wall.Id); }
                            }
                        }
                    }

                    if (isolationers.Count == 0)
                    {
                        MessageBox.Show(string.Format("У помещения №{0} '{1}' нет или не определены детали", roomNumber, roomName));
                    }
                    else
                    {
                        doc.ActiveView.PartsVisibility = PartsVisibility.ShowPartsOnly;
                        doc.ActiveView.DisableTemporaryViewMode(TemporaryViewMode.TemporaryHideIsolate);
                        транзакция.Commit();
                        транзакция.Start();
                        doc.ActiveView.IsolateElementsTemporary(isolationers);
                    }

                }
                else
                {
                    MessageBox.Show("Следующее помещение не имеет номера");
                    args.Application.ActiveUIDocument.Selection.Elements.Clear();
                    args.Application.ActiveUIDocument.Selection.Elements.Add(currentRoom);
                }

            }


            catch (Exception ee)
            {
                MessageBox.Show(ee.Message);
                транзакция.RollBack();
                return;
            }
            транзакция.Commit(); 
        }

    }
    
    [Autodesk.Revit.Attributes.Transaction(Autodesk.Revit.Attributes.TransactionMode.Manual)]
    [Autodesk.Revit.Attributes.Regeneration(Autodesk.Revit.Attributes.RegenerationOption.Manual)]
    [Autodesk.Revit.Attributes.Journaling(Autodesk.Revit.Attributes.JournalingMode.NoCommandData)]
    public class DeterminePartsOfRoom : IExternalCommand
    {
        double LoCoord(XYZ v)
        {
            return Math.Min(Math.Min(Math.Abs(v.X), Math.Abs(v.Y)), Math.Abs(v.Z));
        }

        private static UIApplication Revit = null;

        public Autodesk.Revit.UI.Result Execute(ExternalCommandData revit,
                                               ref string message,
                                               ElementSet elements)
        {

            Revit = revit.Application;
            Autodesk.Revit.DB.Document doc = Revit.ActiveUIDocument.Document;
            Autodesk.Revit.DB.Transaction транзакция = new Autodesk.Revit.DB.Transaction(doc, "MarkWallPartsByRoom");
            string postMessage = "Готово";
            транзакция.Start();

            try
            {
                IList<Element> коллекцияКомнат = StartupClass.PlacedRoomFilter(doc);

                SpatialElementGeometryCalculator calculator = new SpatialElementGeometryCalculator(doc);
                ElementCategoryFilter wallCategoryFilter = new ElementCategoryFilter(BuiltInCategory.OST_Walls);
                ElementCategoryFilter roofCategoryFilter = new ElementCategoryFilter(BuiltInCategory.OST_Roofs);
                ElementCategoryFilter ceilingCategoryFilter = new ElementCategoryFilter(BuiltInCategory.OST_Ceilings);
                ElementCategoryFilter floorCategoryFilter = new ElementCategoryFilter(BuiltInCategory.OST_Floors);
                List<ElementFilter> filterList = new List<ElementFilter>(3);
                filterList.Add(roofCategoryFilter);
                filterList.Add(ceilingCategoryFilter);
                filterList.Add(floorCategoryFilter);
                LogicalOrFilter ceilFloorFilter = new LogicalOrFilter(filterList);

                if (коллекцияКомнат.Count == 0)
                    throw new Exception("Помещения отстутвуют!");

                string report = "";
                int zeroVolumeCounter = 0;

                foreach (Element room in коллекцияКомнат)
                {
                    
                    Parameter roomNumberParam = room.get_Parameter(BuiltInParameter.ROOM_NUMBER);
                    string roomNumber = roomNumberParam.AsString();
                    Parameter roomNameParam = room.get_Parameter(BuiltInParameter.ROOM_NAME);
                    string roomName = roomNameParam.AsString();

                    if (((Autodesk.Revit.DB.Architecture.Room)room).Volume == 0.0)
                    {
                        report += string.Format("{0}-{1}\n", roomNumber, roomName);
                        zeroVolumeCounter ++;
                        continue;
                    }

                    // Get room geometry & boundaries          
                    SpatialElementGeometryResults results = calculator.CalculateSpatialElementGeometry((SpatialElement)room);

                    // Get solid geometry so we can examine each face
                    Solid geometry = results.GetGeometry();

                    foreach (Face face in geometry.Faces)
                    {
                        if (face is PlanarFace)
                        {
                            PlanarFace roomPlanarFace = (PlanarFace)face;
                            XYZ roomFaceNormal = roomPlanarFace.Normal.Normalize().Negate();

                            // Get list of roof boundary subfaces for a given face
                            IList<SpatialElementBoundarySubface> boundaryFaces = results.GetBoundaryFaceInfo(face);
                            foreach (SpatialElementBoundarySubface boundaryFace in boundaryFaces)
                            {
                                // Get boundary element
                                LinkElementId boundaryElementId = boundaryFace.SpatialBoundaryElement;

                                // Only considering local file room bounding elements
                                ElementId localElementId = boundaryElementId.HostElementId;

                                if (localElementId == ElementId.InvalidElementId)
                                    continue;

                                // Evaluate if element meets criteria using PassesFilter()
                                if (wallCategoryFilter.PassesFilter(doc, localElementId))
                                {
                                    if (PartUtils.HasAssociatedParts(doc, localElementId))
                                    {
                                        List<ElementId> parts = new List<ElementId>();
                                        parts.AddRange(PartUtils.GetAssociatedParts(doc, localElementId, true, true));
                                        foreach (ElementId id in parts)
                                        {
                                            Element part = doc.GetElement(id);
                                            GeometryElement geometryElement = part.get_Geometry(new Options());
                                            foreach (GeometryObject geometryObject in geometryElement)
                                            {
                                                if (geometryObject is Solid)
                                                {
                                                    Solid partSolid = geometryObject as Solid;
                                                    BoundingBoxXYZ bb = partSolid.GetBoundingBox();
                                                    double tickness = LoCoord(bb.Max - bb.Min) / 2.0 + 0.164;
                                                    foreach (Face partFace in partSolid.Faces)
                                                    {
                                                        if (partFace is PlanarFace)
                                                        {
                                                            PlanarFace partPlanarFace = (PlanarFace)partFace;
                                                            XYZ n = partPlanarFace.Normal.Normalize();
                                                            double dis = Math.Abs((roomPlanarFace.Origin - partPlanarFace.Origin).DotProduct(n));

                                                            if (dis < 0.002 && n.IsAlmostEqualTo(roomFaceNormal))
                                                            {
                                                                bool coincident = false;
                                                                XYZ middle = partSolid.ComputeCentroid();
                                                                middle = middle + n * tickness;
                                                                coincident = ((Autodesk.Revit.DB.Architecture.Room)room).IsPointInRoom(middle);

                                                                if (coincident)
                                                                {
                                                                    Parameter p = StartupClass.GetOrCreateParameter(Revit, part, "Номер помещения", ParameterType.Text, true);
                                                                    p.Set(roomNumber);
                                                                    p = StartupClass.GetOrCreateParameter(Revit, part, "Имя помещения", ParameterType.Text, true);
                                                                    p.Set(roomName);
                                                                    StartupClass.GetOrCreateParameter(Revit, part, "Перечень помещений", ParameterType.Text, true);
                                                                }
                                                            }
                                                        }
                                                    }
                                                }
                                            }
                                        }
                                    } // has parts
                                    else // Стена не деленная на детали
                                    {
                                        // Записываем атрибуты помещения в параметры стены
                                        Wall wall = doc.GetElement(localElementId) as Wall;
                                        if (wall != null)
                                        {
                                            Parameter p = wall.WallType.get_Parameter(BuiltInParameter.ALL_MODEL_MODEL);
                                            if (string.Compare("Отделка", p.AsString()) == 0)
                                            {
                                                p = StartupClass.GetOrCreateParameter(Revit, wall, "Номер помещения", ParameterType.Text, true);
                                                p.Set(roomNumber);
                                                p = StartupClass.GetOrCreateParameter(Revit, wall, "Имя помещения", ParameterType.Text, true);
                                                p.Set(roomName);
                                                StartupClass.GetOrCreateParameter(Revit, wall, "Перечень помещений", ParameterType.Text, true);
                                            }
                                        }
                                    };
                                }

                                else if (ceilFloorFilter.PassesFilter(doc, localElementId))
                                {
                                    // Записываем атрибуты помещения в параметры потолка
                                    Element e = doc.GetElement(localElementId);
                                    if (e != null)
                                    {
                                        //Parameter p = e.get_Parameter(BuiltInParameter.ALL_MODEL_MODEL);
                                        //if (string.Compare("Отделка", p.AsString()) == 0)
                                        {
                                            Parameter p = StartupClass.GetOrCreateParameter(Revit, e, "Номер помещения", ParameterType.Text, true);
                                            p.Set(roomNumber);
                                            p = StartupClass.GetOrCreateParameter(Revit, e, "Имя помещения", ParameterType.Text, true);
                                            p.Set(roomName);
                                            StartupClass.GetOrCreateParameter(Revit, e, "Перечень помещений", ParameterType.Text, true);
                                        }
                                    }
                                }
                            }
                        }
                    }
                } // foreach in коллекцияКомнат

                if (report.Length > 0)
                {
                    if (zeroVolumeCounter == коллекцияКомнат.Count)
                        postMessage = "Все помещения имеют нулевой объем. Проверьте опции расчета помещений (Архитектура -> Помещения и Зоны -> Расчет площадей и объемов -> переключить Расчеты объемов на Площади и объемы";
                    else
                        postMessage = "Эти помещения не размещены или имеют нулевой объем:\n" + report;
                }   

            }
            catch (Exception ee)
            {
                MessageBox.Show(ee.Message);
                транзакция.RollBack();
                return Autodesk.Revit.UI.Result.Failed;
            }
            транзакция.Commit();

            MessageBox.Show(postMessage, "Определить детали отделки", MessageBoxButtons.OK, MessageBoxIcon.Information);
            return Autodesk.Revit.UI.Result.Succeeded;
        }
    }

    [Autodesk.Revit.Attributes.Transaction(Autodesk.Revit.Attributes.TransactionMode.Manual)]
    [Autodesk.Revit.Attributes.Regeneration(Autodesk.Revit.Attributes.RegenerationOption.Manual)]
    [Autodesk.Revit.Attributes.Journaling(Autodesk.Revit.Attributes.JournalingMode.NoCommandData)]
    public class IsolateNextRoomParts : IExternalCommand
    {
        public Autodesk.Revit.UI.Result Execute(ExternalCommandData revit,
                                               ref string message,
                                               ElementSet elements)
        {

            Autodesk.Revit.DB.Document doc = revit.Application.ActiveUIDocument.Document;
            Autodesk.Revit.DB.Transaction транзакция = new Autodesk.Revit.DB.Transaction(doc, "IsolateNextRoomParts");
            транзакция.Start();

            try
            {
                IList<Element> коллекцияКомнат = StartupClass.PlacedRoomFilter(doc);

                if (коллекцияКомнат.Count > 0)
                {
                    StartupClass.currentRoomIndex++;
                    if (StartupClass.currentRoomIndex >= коллекцияКомнат.Count)
                        StartupClass.currentRoomIndex = 0;
                    Autodesk.Revit.DB.Architecture.Room currentRoom = коллекцияКомнат[StartupClass.currentRoomIndex] as Autodesk.Revit.DB.Architecture.Room;

                    Parameter roomNumberParam = currentRoom.get_Parameter(BuiltInParameter.ROOM_NUMBER);
                    string roomNumber = roomNumberParam.AsString();
                    Parameter roomNameParam = currentRoom.get_Parameter(BuiltInParameter.ROOM_NAME);
                    string roomName = roomNameParam.AsString();
                    StartupClass.roomNumberBox.Value = string.Format("{0}-{1}", roomNumber, roomName);

                    if (roomNumber.Count() > 0)
                    {
                        FilteredElementCollector коллектор = new FilteredElementCollector(doc);
                        ElementCategoryFilter фильтр2 = new ElementCategoryFilter(BuiltInCategory.OST_Walls);
                        ICollection<Element> коллекцияСтен = коллектор.WherePasses(фильтр2).WhereElementIsNotElementType().ToElements();

                        List<ElementId> isolationers = new List<ElementId>();

                        foreach (Wall wall in коллекцияСтен)
                        {
                            if (PartUtils.HasAssociatedParts(doc, wall.Id))
                            {
                                // Добавляем детали стены смежные с помещением
                                List<ElementId> parts = new List<ElementId>();
                                parts.AddRange(PartUtils.GetAssociatedParts(doc, wall.Id, true, true));
                                foreach (ElementId id in parts)
                                {
                                    Element part = doc.GetElement(id);
                                    Parameter p = part.get_Parameter("Номер помещения");
                                    if (p == null)
                                        throw new Exception("У деталей нет параметра 'Номер помещения', запустите команду 'Определить детали стен примыкающие к помещениям'");

                                    if (string.Compare(roomNumber, p.AsString()) == 0) { isolationers.Add(id); }
                                }
                            } // has parts
                            else
                            {
                                // Добавляем стену полностью
                                Parameter p = wall.WallType.get_Parameter(BuiltInParameter.ALL_MODEL_MODEL);
                                if (string.Compare("Отделка", p.AsString()) == 0)
                                {
                                    p = wall.get_Parameter("Номер помещения");
                                    if (p == null)
                                        throw new Exception("У стены нет параметра 'Номер помещения', запустите команду 'Определить детали стен примыкающие к помещениям'");

                                    if (string.Compare(roomNumber, p.AsString()) == 0) { isolationers.Add(wall.Id); }
                                }
                            }
                        }

                        if (isolationers.Count == 0)
                        {
                            MessageBox.Show(string.Format("У помещения №{0} '{1}' нет или не определены детали", roomNumber, roomName));
                        }
                        else
                        {
                            doc.ActiveView.PartsVisibility = PartsVisibility.ShowPartsOnly;
                            doc.ActiveView.DisableTemporaryViewMode(TemporaryViewMode.TemporaryHideIsolate);
                            транзакция.Commit();
                            транзакция.Start();
                            doc.ActiveView.IsolateElementsTemporary(isolationers);
                        }

                    }
                    else
                    {
                        MessageBox.Show("Следующее помещение не имеет номера");
                        revit.Application.ActiveUIDocument.Selection.Elements.Clear();
                        revit.Application.ActiveUIDocument.Selection.Elements.Add(currentRoom);
                    }

                }
            }


            catch (Exception ee)
            {
                MessageBox.Show(ee.Message);
                транзакция.RollBack();
                return Autodesk.Revit.UI.Result.Failed;
            }
            транзакция.Commit(); 
            return Autodesk.Revit.UI.Result.Succeeded;
        }
    }

    [Autodesk.Revit.Attributes.Transaction(Autodesk.Revit.Attributes.TransactionMode.Manual)]
    [Autodesk.Revit.Attributes.Regeneration(Autodesk.Revit.Attributes.RegenerationOption.Manual)]
    [Autodesk.Revit.Attributes.Journaling(Autodesk.Revit.Attributes.JournalingMode.NoCommandData)]
    public class IsolatePreviousRoomParts : IExternalCommand
    {
        public Autodesk.Revit.UI.Result Execute(ExternalCommandData revit,
                                               ref string message,
                                               ElementSet elements)
        {
            Autodesk.Revit.DB.Document doc = revit.Application.ActiveUIDocument.Document;
            Autodesk.Revit.DB.Transaction транзакция = new Autodesk.Revit.DB.Transaction(doc, "IsolateNextRoomParts");
            транзакция.Start();

            try
            {
                IList<Element> коллекцияКомнат = StartupClass.PlacedRoomFilter(doc);

                if (коллекцияКомнат.Count > 0)
                {
                    StartupClass.currentRoomIndex--;
                    if (StartupClass.currentRoomIndex < 0)
                        StartupClass.currentRoomIndex = коллекцияКомнат.Count - 1;
                    Autodesk.Revit.DB.Architecture.Room currentRoom = коллекцияКомнат[StartupClass.currentRoomIndex] as Autodesk.Revit.DB.Architecture.Room;

                    Parameter roomNumberParam = currentRoom.get_Parameter(BuiltInParameter.ROOM_NUMBER);
                    string roomNumber = roomNumberParam.AsString();
                    Parameter roomNameParam = currentRoom.get_Parameter(BuiltInParameter.ROOM_NAME);
                    string roomName = roomNameParam.AsString();
                    StartupClass.roomNumberBox.Value = string.Format("{0}-{1}", roomNumber, roomName);

                    if (roomNumber.Count() > 0)
                    {
                        FilteredElementCollector коллектор = new FilteredElementCollector(doc);
                        ElementCategoryFilter фильтр2 = new ElementCategoryFilter(BuiltInCategory.OST_Walls);
                        ICollection<Element> коллекцияСтен = коллектор.WherePasses(фильтр2).WhereElementIsNotElementType().ToElements();

                        List<ElementId> isolationers = new List<ElementId>();

                        foreach (Wall wall in коллекцияСтен)
                        {
                            if (PartUtils.HasAssociatedParts(doc, wall.Id))
                            {
                                // Добавляем детали стены смежные с помещением
                                List<ElementId> parts = new List<ElementId>();
                                parts.AddRange(PartUtils.GetAssociatedParts(doc, wall.Id, true, true));
                                foreach (ElementId id in parts)
                                {
                                    Element part = doc.GetElement(id);
                                    Parameter p = part.get_Parameter("Номер помещения");
                                    if (p == null)
                                        throw new Exception("У деталей нет параметра 'Номер помещения', запустите команду 'Определить детали стен примыкающие к помещениям'");

                                    if (string.Compare(roomNumber, p.AsString()) == 0) { isolationers.Add(id); }
                                }
                            } // has parts
                            else
                            {
                                // Добавляем стену полностью
                                Parameter p = wall.WallType.get_Parameter(BuiltInParameter.ALL_MODEL_MODEL);
                                if (string.Compare("Отделка", p.AsString()) == 0) 
                                {
                                    p = wall.get_Parameter("Номер помещения");
                                    if (p == null)
                                        throw new Exception("У стены нет параметра 'Номер помещения', запустите команду 'Определить детали стен примыкающие к помещениям'");

                                    if (string.Compare(roomNumber, p.AsString()) == 0) { isolationers.Add(wall.Id); }                                    
                                }
                            }
                        }

                        if (isolationers.Count == 0)
                        {
                            MessageBox.Show(string.Format("У помещения №{0} '{1}' нет или не определены детали", roomNumber, roomName));
                        }
                        else
                        {
                            doc.ActiveView.PartsVisibility = PartsVisibility.ShowPartsOnly;
                            doc.ActiveView.DisableTemporaryViewMode(TemporaryViewMode.TemporaryHideIsolate);
                            транзакция.Commit();
                            транзакция.Start();
                            doc.ActiveView.IsolateElementsTemporary(isolationers);
                        }

                    }
                    else
                    {
                        MessageBox.Show("Следующее помещение не имеет номера");
                        revit.Application.ActiveUIDocument.Selection.Elements.Clear();
                        revit.Application.ActiveUIDocument.Selection.Elements.Add(currentRoom);
                    }

                }
            }


            catch (Exception ee)
            {
                MessageBox.Show(ee.Message);
                транзакция.RollBack();
                return Autodesk.Revit.UI.Result.Failed;
            }
            транзакция.Commit();
            return Autodesk.Revit.UI.Result.Succeeded;
        }
    }

    [Autodesk.Revit.Attributes.Transaction(Autodesk.Revit.Attributes.TransactionMode.Manual)]
    [Autodesk.Revit.Attributes.Regeneration(Autodesk.Revit.Attributes.RegenerationOption.Manual)]
    [Autodesk.Revit.Attributes.Journaling(Autodesk.Revit.Attributes.JournalingMode.NoCommandData)]
    public class WorkplaneDivideParts : IExternalCommand
    {
        public Autodesk.Revit.UI.Result Execute(ExternalCommandData revit,
                                               ref string message,
                                               ElementSet elements)
        {
            Autodesk.Revit.DB.Document doc = revit.Application.ActiveUIDocument.Document;
            Autodesk.Revit.DB.Transaction транзакция = new Autodesk.Revit.DB.Transaction(doc, "WorkplaneDivideParts");
            транзакция.Start();

            try
            {
                Autodesk.Revit.UI.Selection.SelElementSet набор = revit.Application.ActiveUIDocument.Selection.Elements;
                SketchPlane plane = doc.ActiveView.SketchPlane;
                

                if (plane != null)
                {
                    IList<Part> детали = new List<Part>();
                    XYZ workNormal = plane.GetPlane().Normal;
                    double d1 = -plane.GetPlane().Origin.DotProduct(workNormal); 

                    foreach (Element элемент in набор)
                    {
                        if ((BuiltInCategory)элемент.Category.Id.IntegerValue == BuiltInCategory.OST_Parts)
                        {
                            Part деталь = элемент as Part;
                            if ((BuiltInCategory)деталь.OriginalCategoryId.IntegerValue == BuiltInCategory.OST_Walls)
                            {
                                детали.Add(деталь);
                            }
                        }
                    }

                    if (детали.Count > 0)
                    {
                        foreach (Part деталь in детали)
                        {
                            ICollection<LinkElementId> walls = деталь.GetSourceElementIds();
                            Element wall = doc.GetElement(walls.First().HostElementId);
                            if (wall == null) continue;
                            LocationCurve loc = wall.Location as LocationCurve;
                            if (loc == null) continue;
                            Line line = loc.Curve as Line;
                            if (line == null) continue;
                            Autodesk.Revit.DB.Transform rotation = Autodesk.Revit.DB.Transform.CreateRotationAtPoint(XYZ.BasisZ, 0.5 * Math.PI, line.Origin);
                            XYZ normal = rotation.OfVector(line.Direction).Normalize();
                            SketchPlane divisionSketchPlane = SketchPlane.Create(doc, new Plane(normal, line.Origin));
                            double d2 = -line.Origin.DotProduct(normal);
                            double koffA = (normal.X + workNormal.X);
                            double koffB = (normal.Y + workNormal.Y);
                            double koffD = d1 + d2;
                            double koffC = -(normal.Z + workNormal.Z);
                            if (koffC == 0.0) continue;
                            XYZ start = new XYZ(line.Origin.X, line.Origin.Y, (koffA * line.Origin.X + koffB * line.Origin.Y + koffD) / koffC);
                            start -= line.Direction;
                            XYZ end = line.Evaluate(1.0, true);
                            end = new XYZ(end.X, end.Y, (koffA * end.X + koffB * end.Y + koffD) / koffC);
                            end += line.Direction;
                            ICollection<ElementId> isectElements = new Collection<ElementId>();

                            Line isecLine = Line.CreateBound(start, end);
                            IList<Curve> curves = new List<Curve>(1);
                            curves.Add(isecLine);
                            ICollection<ElementId> наразделку = new Collection<ElementId>();
                            наразделку.Add(деталь.Id);

                            PartMaker sadjukTheMaker = PartUtils.DivideParts(doc, наразделку, isectElements, curves, divisionSketchPlane.Id);
                        }
                    }
                    else
                    {
                        MessageBox.Show("Не выбрано ни одной детали стены");
                        транзакция.RollBack();
                        return Autodesk.Revit.UI.Result.Failed;
                    }
                }
                else
                {
                    MessageBox.Show("У вида нет рабочей плоскости");
                    транзакция.RollBack();
                    return Autodesk.Revit.UI.Result.Failed;
                }


            }
            catch (Exception ee)
            {
                MessageBox.Show(ee.Message);
                транзакция.RollBack();
                return Autodesk.Revit.UI.Result.Failed;
            }
            транзакция.Commit();

            
            return Autodesk.Revit.UI.Result.Succeeded;
        }
    }

    struct TakeoffHeadText 
    {
        public string takeoffName { get; set; }
        public string roomNameOrNumber { get; set; }
        public string finishingType { get; set; }
        public string ceillingMat { get; set; }
        public string ceillingArea { get; set; }
        public string wallMat { get; set; }
        public string wallArea { get; set; }
        public string note { get; set; } 
    }

    [Autodesk.Revit.Attributes.Transaction(Autodesk.Revit.Attributes.TransactionMode.Manual)]
    [Autodesk.Revit.Attributes.Regeneration(Autodesk.Revit.Attributes.RegenerationOption.Manual)]
    [Autodesk.Revit.Attributes.Journaling(Autodesk.Revit.Attributes.JournalingMode.NoCommandData)]
    public class CreateFinishingSchedule : IExternalCommand
    {
        private const double k = 1 / 3.048;
        private const double кМетрамКвадратным = 0.09290304;

        static readonly TakeoffHeadText ukrHead = new TakeoffHeadText
        {
            takeoffName = "ВІДОМІСТЬ ОПРЯДЖЕННЯ ПРИМІЩЕНЬ", 
            roomNameOrNumber = "Найменування \nабо номер приміщення",
            finishingType = "Вид опорядження елементів інтер'єрів",
            ceillingMat = "Стеля",
            ceillingArea = "Площа, \nм²",
            wallMat = "Стіни, колони, \nперегородки",
            wallArea = "Площа, \nм²",
            note = "Примітки"
        };

        static readonly TakeoffHeadText rusHead = new TakeoffHeadText
        {
            takeoffName = "ВЕДОМОСТЬ ОТДЕЛКИ ПОМЕЩЕНИЙ",
            roomNameOrNumber = "Наименование \nили номер помещения",
            finishingType = "Вид отделки элементов интерьеров",
            ceillingMat = "Потолок",
            ceillingArea = "Площадь, \nм²",
            wallMat = "Стены, колонны, \nперегородки",
            wallArea = "Площадь, \nм²",
            note = "Примечания"
        };

        Autodesk.Revit.DB.Document doc = null;

        private void SetLineStyle(DetailCurve curve, string styleName)
        {
            ICollection<ElementId> lsColl = curve.GetLineStyleIds();
            foreach (ElementId id in lsColl)
            {
                Element e = doc.GetElement(id);
                if (e.Name == styleName)
                {
                    curve.LineStyle = e;
                    return;
                }
            }
        }

        private void DrawLine(Autodesk.Revit.DB.View view, double x1, double y1, double x2, double y2, Category cat)
        {
            Line line = Line.CreateBound(new XYZ(x1 * k, -y1 * k, 0), new XYZ(x2 * k, -y2 * k, 0));
            DetailCurve curve = doc.Create.NewDetailCurve(view, line);
            SetLineStyle(curve, cat.Name);
        }

        private double GetTextHeight(Autodesk.Revit.DB.View view, TextNote t)
        {
            BoundingBoxXYZ bb = t.get_BoundingBox(view);
            double h = bb.Max.Y - bb.Min.Y;
            return h / k;
        }

        private TextNote DrawTextCenter(Autodesk.Revit.DB.View view, double x, double y, double w, TextAlignFlags align, string text)
        {
            TextNote t;
            if (align == TextAlignFlags.TEF_ALIGN_CENTER)
            {
                t = doc.Create.NewTextNote(view, new XYZ(k * (x + w * 0.5), -y * k, 0), XYZ.BasisX, XYZ.BasisY, w * k / 100, align, text);
            }
            else
            {
                t = doc.Create.NewTextNote(view, new XYZ(k * x, -y * k, 0), XYZ.BasisX, XYZ.BasisY, w * k / 100, align, text);
            }
            BoundingBoxXYZ bb = t.get_BoundingBox(view);
            double h = bb.Max.Y - bb.Min.Y;
            t.Coord = new XYZ(t.Coord.X, t.Coord.Y + (h / 2), 0);
            return t;
        }

        private TextNote DrawTextTop(Autodesk.Revit.DB.View view, double x, double y, double w, TextAlignFlags align, string text)
        {
            TextNote t;
            if (align == TextAlignFlags.TEF_ALIGN_CENTER)
            {
                t = doc.Create.NewTextNote(view, new XYZ(k * (x + w * 0.5), -y * k, 0), XYZ.BasisX, XYZ.BasisY, w * k / 100, align, text);
            }
            else
            {
                t = doc.Create.NewTextNote(view, new XYZ(k * x, -y * k, 0), XYZ.BasisX, XYZ.BasisY, w * k / 100, align, text);
            }
            return t;
        }
        private string GetUniqueViewName(string origin)
        {
            FilteredElementCollector collector = new FilteredElementCollector(doc);
            FilteredElementIterator itor = collector.OfClass(typeof(Autodesk.Revit.DB.View)).GetElementIterator();
            int i = 0;
            string name = origin;
            bool f = true;
            while (f)
            {
                f = false;
                itor.Reset();
                while (itor.MoveNext())
                {
                    Autodesk.Revit.DB.View view = itor.Current as Autodesk.Revit.DB.View;
                    // skip view templates because they're invisible in project browser
                    if (null == view || view.IsTemplate)
                    {
                        continue;
                    }
                    else
                    {
                        if (name == view.Name)
                        {
                            i++;
                            name = string.Format("{0} ({1})", origin, i);
                            f = true;
                            break;
                        }
                    }
                }
            }
            return name;
        }

        private string GetRoomLabel(Element finishElem)
        {
            Parameter p1 = finishElem.get_Parameter("Номер помещения");
            Parameter p2 = finishElem.get_Parameter("Имя помещения");
            if (p1 == null || p2 == null) return null; // нуль тебе, а не метку

            switch (StartupClass.firstColumnValue)
            {
                case 0: return p1.AsString();
                case 1: return p2.AsString();
                case 2: return string.Format("{0}-{1}", p1.AsString(), p2.AsString());
            }
            return "Ошибка";
        }

        public Autodesk.Revit.UI.Result Execute(ExternalCommandData revit,
                                               ref string message,
                                               ElementSet elements)
        {
            doc = revit.Application.ActiveUIDocument.Document;
            Autodesk.Revit.DB.Transaction транзакция = new Autodesk.Revit.DB.Transaction(doc, "CreateFinishingSchedule");
            ViewDrafting view = null;

            транзакция.Start();
            try
            {
                Parameter viewPhaseParam = doc.ActiveView.get_Parameter(BuiltInParameter.VIEW_PHASE);
                if (viewPhaseParam == null)
                {
                    throw new Exception("Активируйте вид у которого есть параметр 'Тип системы'");
                }
                ElementId phaseId = viewPhaseParam.AsElementId();

                view = doc.Create.NewViewDrafting();
                view.Name = GetUniqueViewName("Ведомость отделки помещений");
                view.Scale = 100;


                Category lineCat = doc.Settings.Categories.get_Item(BuiltInCategory.OST_Lines);
                Category lineW1Cat = null;
                Category lineW4Cat = null;
                foreach (Category cat in lineCat.SubCategories)
                {
                    if (cat.Name.StartsWith("Черная №1 сплошная"))
                    {
                        lineW1Cat = cat;
                        break;
                    }
                }
                if (lineW1Cat == null)
                {
                    lineW1Cat = doc.Settings.Categories.NewSubcategory(lineCat, "Черная №1 сплошная");
                    lineW1Cat.LineColor = new Autodesk.Revit.DB.Color(0, 0, 0);
                    lineW1Cat.SetLineWeight(1, GraphicsStyleType.Projection);
                }

                foreach (Category cat in lineCat.SubCategories)
                {
                    if (cat.Name.StartsWith("Черная №4 сплошная"))
                    {
                        lineW4Cat = cat;
                        break;
                    }
                }
                if (lineW4Cat == null)
                {
                    lineW4Cat = doc.Settings.Categories.NewSubcategory(lineCat, "Черная №4 сплошная");
                    lineW4Cat.LineColor = new Autodesk.Revit.DB.Color(0, 0, 0);
                    lineW4Cat.SetLineWeight(4, GraphicsStyleType.Projection);
                }

                DrawLine(view, 0, 0, 185, 0, lineW4Cat);
                DrawLine(view, 0, 15, 185, 15, lineW4Cat);
                DrawLine(view, 40, 5, 130, 5, lineW4Cat);
                DrawLine(view, 0, 0, 0, 15, lineW4Cat);
                DrawLine(view, 40, 0, 40, 15, lineW4Cat);
                DrawLine(view, 70, 5, 70, 15, lineW4Cat);
                DrawLine(view, 85, 5, 85, 15, lineW4Cat);
                DrawLine(view, 115, 5, 115, 15, lineW4Cat);
                DrawLine(view, 130, 0, 130, 15, lineW4Cat);
                DrawLine(view, 185, 0, 185, 15, lineW4Cat);

                TakeoffHeadText headText;
                //if (InputLanguage.CurrentInputLanguage.Culture.Name.StartsWith("uk-UA"))
                if (StartupClass.takeoffLanguage == 1)
                {
                    headText = ukrHead;
                }
                else
                {
                    headText = rusHead;
                }

                DrawTextCenter(view, 0, -4, 185, TextAlignFlags.TEF_ALIGN_CENTER, headText.takeoffName);
                DrawTextCenter(view, 0, 7.5, 40, TextAlignFlags.TEF_ALIGN_CENTER, headText.roomNameOrNumber);
                DrawTextCenter(view, 40, 2.5, 90, TextAlignFlags.TEF_ALIGN_CENTER, headText.finishingType);
                DrawTextCenter(view, 40, 10, 30, TextAlignFlags.TEF_ALIGN_CENTER, headText.ceillingMat);
                DrawTextCenter(view, 70, 10, 15, TextAlignFlags.TEF_ALIGN_CENTER, headText.ceillingArea);
                DrawTextCenter(view, 85, 10, 30, TextAlignFlags.TEF_ALIGN_CENTER, headText.wallMat);
                DrawTextCenter(view, 115, 10, 15, TextAlignFlags.TEF_ALIGN_CENTER, headText.wallArea);
                DrawTextCenter(view, 130, 7.5, 55, TextAlignFlags.TEF_ALIGN_CENTER, headText.note);

                FilteredElementCollector коллектор = new FilteredElementCollector(doc);
                ElementCategoryFilter partFilter = new ElementCategoryFilter(BuiltInCategory.OST_Parts);
                ElementCategoryFilter wallFilter = new ElementCategoryFilter(BuiltInCategory.OST_Walls);
                LogicalOrFilter filter = new LogicalOrFilter(partFilter, wallFilter);
                ICollection<Element> сollection = коллектор.WherePasses(filter).WhereElementIsNotElementType().ToElements();
                Dictionary<string, List<Element>> wallDictionary = new Dictionary<string, List<Element>>();

                // Создается словарь в котором каждому помещению соответствует список деталей отделки
                foreach (Element e in сollection)
                {
                    Parameter phaseParam = e.get_Parameter(BuiltInParameter.PHASE_DEMOLISHED);
                    if (phaseParam.AsElementId() != ElementId.InvalidElementId)
                        continue;
                    string label = GetRoomLabel(e);
                    if (label == null || label.Length == 0) continue;

                    Part p = e as Part;
                    if (p != null)
                    {
                        if (p.Excluded) continue;
                        if (PartUtils.HasAssociatedParts(doc, e.Id)) continue;
                    }
                    Wall w = e as Wall;
                    if (w != null)
                    {
                        Parameter modelParam = w.WallType.get_Parameter(BuiltInParameter.ALL_MODEL_MODEL);
                        if (string.Compare("Отделка", modelParam.AsString()) != 0) continue;
                    }

                    if (wallDictionary.ContainsKey(label))
                    {
                        wallDictionary[label].Add(e);
                    }
                    else
                    {
                        wallDictionary.Add(label, new List<Element>());
                        wallDictionary[label].Add(e);
                    }
                }

                коллектор = new FilteredElementCollector(doc);
                ElementCategoryFilter ceilingFilter = new ElementCategoryFilter(BuiltInCategory.OST_Ceilings);
                ElementCategoryFilter roofFilter = new ElementCategoryFilter(BuiltInCategory.OST_Roofs);
                LogicalOrFilter filter1 = new LogicalOrFilter(ceilingFilter, roofFilter);
                сollection = коллектор.WherePasses(filter1).WhereElementIsNotElementType().ToElements();
                Dictionary<string, List<Element>> ceilDictionary = new Dictionary<string, List<Element>>();

                // Создается словарь в котором каждому помещению соответствует список потолков
                foreach (Element e in сollection)
                {
                    Parameter phaseParam = e.get_Parameter(BuiltInParameter.PHASE_DEMOLISHED);
                    if (phaseParam.AsElementId() != ElementId.InvalidElementId)
                        continue;
                    string label = GetRoomLabel(e);
                    if (label == null || label.Length == 0) continue;

                    if (ceilDictionary.ContainsKey(label))
                    {
                        ceilDictionary[label].Add(e);
                    }
                    else
                    {
                        ceilDictionary.Add(label, new List<Element>());
                        ceilDictionary[label].Add(e);
                    }
                }
                
                double pos = 15.0f;

                if (StartupClass.groupingByMaterial) // группируем помещения с одинаковой отделкой
                {
                    // Создается словарь в котором сумме названий материалов отделки соответствует список деталей стен и потолков 
                    Dictionary<string, List<Element>> matDictionary = new Dictionary<string, List<Element>>();
                    foreach (string key in wallDictionary.Keys)
                    {
                        ArrayList matSum = new ArrayList();
                        List<Element> list1;
                        if (ceilDictionary.TryGetValue(key, out list1))
                        {
                            ICollection<ElementId> matIds = null;
                            foreach (Element e in list1)
                            {
                                if (e is Ceiling) { matIds = (e as Ceiling).GetMaterialIds(false); }
                                if (e is RoofBase) { matIds = (e as RoofBase).RoofType.GetMaterialIds(false); }
                                foreach (ElementId matId in matIds)
                                {
                                    Material mat = doc.GetElement(matId) as Material;
                                    if (!matSum.Contains(mat.Name)) { matSum.Add(mat.Name); }
                                }
                            }
                        }
                        else list1 = null;

                        List<Element> list2 = wallDictionary[key];
                        foreach (Element e in list2)
                        {
                            ICollection<ElementId> matIds = null;
                            if (e is Part) { matIds = (e as Part).GetMaterialIds(false); }
                            if (e is Wall) { matIds = (e as Wall).WallType.GetMaterialIds(false); }
                            foreach (ElementId matId in matIds)
                            {
                                Material mat = doc.GetElement(matId) as Material;
                                if (!matSum.Contains(mat.Name)) { matSum.Add(mat.Name); }
                            }
                        }

                        string s = string.Join(" ", matSum.ToArray());
                        List<Element> outList = null;
                        if (matDictionary.TryGetValue(s, out outList))
                        {
                            if (list1 != null)
                                outList.AddRange(list1);
                            outList.AddRange(list2);
                        }
                        else
                        {
                            List<Element> newList = new List<Element>();
                            if (list1 != null)
                                newList.AddRange(list1);
                            newList.AddRange(list2);
                            matDictionary.Add(s, newList);
                        }
                    }

                    // Заполняем параметр Перечень помещений для каждого элемента отделки
                    foreach (string key in matDictionary.Keys)
                    {
                        IList<Element> list = matDictionary[key];
                        ArrayList roomEnums = new ArrayList();

                        foreach (Element e in list)
                        {
                            string num = GetRoomLabel(e);
                            if (!roomEnums.Contains(num)) { roomEnums.Add(num); }
                        }
                        roomEnums.Sort();
                        string oneline = string.Join(",", roomEnums.ToArray());

                        Dictionary<string, double> ceilDictionary_ = new Dictionary<string, double>();
                        Dictionary<string, double> wallDictionary_ = new Dictionary<string, double>();

                        foreach (Element e in list)
                        {
                            Parameter param = e.get_Parameter("Перечень помещений");
                            param.Set(oneline);

                            ICollection<ElementId> matIds = e.GetMaterialIds(false);
                            Material m = doc.GetElement(matIds.Last()) as Material;
                            string desc = m.get_Parameter(BuiltInParameter.ALL_MODEL_DESCRIPTION).AsString();
                            if (desc.Length == 0) desc = m.Name;
                            double area;
                            if ((e is Ceiling) || (e is RoofBase))
                            {
                                if (ceilDictionary_.TryGetValue(desc, out area))
                                {
                                    area += e.GetMaterialArea(m.Id, false);
                                    ceilDictionary_[desc] = area;
                                }
                                else
                                {
                                    area = e.GetMaterialArea(m.Id, false);
                                    ceilDictionary_.Add(desc, area);
                                }
                            }
                            else if ((e is Part) || (e is Wall))
                            {
                                if (wallDictionary_.TryGetValue(desc, out area))
                                {
                                    area += e.GetMaterialArea(m.Id, false);
                                    wallDictionary_[desc] = area;
                                }
                                else
                                {
                                    area = e.GetMaterialArea(m.Id, false);
                                    wallDictionary_.Add(desc, area);
                                }
                            }
                        }

                        double h1 = 0.0f;
                        int i = 0;
                        foreach (string desc in ceilDictionary_.Keys)
                        {
                            TextNote t = DrawTextTop(view, 40, pos + h1, 30, TextAlignFlags.TEF_ALIGN_CENTER, desc);
                            double area = кМетрамКвадратным * ceilDictionary_[desc];
                            double h3 = GetTextHeight(view, t);
                            DrawTextCenter(view, 70, pos + h1 + h3 / 2, 15, TextAlignFlags.TEF_ALIGN_CENTER, string.Format("{0:0.00}", area));
                            h1 += h3;
                            i++;
                            if (i < ceilDictionary_.Count)
                                DrawLine(view, 40, pos + h1, 85, pos + h1, lineW1Cat);
                        }

                        double h2 = 0.0f;
                        i = 0;
                        foreach (string desc in wallDictionary_.Keys)
                        {
                            TextNote t = DrawTextTop(view, 85, pos + h2, 30, TextAlignFlags.TEF_ALIGN_CENTER, desc);
                            double area = кМетрамКвадратным * wallDictionary_[desc];
                            double h3 = GetTextHeight(view, t);
                            DrawTextCenter(view, 115, pos + h2 + h3 / 2, 15, TextAlignFlags.TEF_ALIGN_CENTER, string.Format("{0:0.00}", area));
                            h2 += h3;
                            i++;
                            if (i < wallDictionary_.Count)
                                DrawLine(view, 85, pos + h2, 130, pos + h2, lineW1Cat);
                        }
                        double maxH = (h1 > h2) ? h1 : h2;

                        DrawTextCenter(view, 0, pos + maxH / 2, 40, TextAlignFlags.TEF_ALIGN_CENTER, oneline);
                        pos += maxH;
                        DrawLine(view, 0, pos, 185, pos, lineW1Cat);

                    } // foreach группа материалов
                }
                else // каждое помещение отдельной строкой
                {
                    // Сортируем словари по возрастанию меток помещений
                    wallDictionary = wallDictionary.OrderBy(x => x.Key).ToDictionary(x => x.Key, x => x.Value);
                    ceilDictionary = ceilDictionary.OrderBy(x => x.Key).ToDictionary(x => x.Key, x => x.Value);

                    foreach (string key in wallDictionary.Keys)
                    {
                        Dictionary<ElementId, double> wallMatDictionary = new Dictionary<ElementId, double>();
                        Dictionary<ElementId, double> ceilMatDictionary = new Dictionary<ElementId, double>();

                        List<Element> list1 = wallDictionary[key];
                        foreach (Element e in list1)
                        {
                            double area;
                            ICollection<ElementId> matIds = null;
                            if (e is Part) { matIds = (e as Part).GetMaterialIds(false); }
                            if (e is Wall) { matIds = (e as Wall).WallType.GetMaterialIds(false); }
                            foreach (ElementId matId in matIds)
                            {
                                if (wallMatDictionary.TryGetValue(matId, out area))
                                {
                                    area += e.GetMaterialArea(matId, false);
                                    wallMatDictionary[matId] = area;
                                }
                                else
                                {
                                    area = e.GetMaterialArea(matId, false);
                                    wallMatDictionary.Add(matId, area);
                                }
                            }
                        }

                        if (ceilDictionary.TryGetValue(key, out list1))
                        {
                            foreach (Element e in list1)
                            {
                                double area;
                                ICollection<ElementId> matIds = null;
                                if (e is Ceiling) { matIds = (e as Ceiling).GetMaterialIds(false); }
                                if (e is RoofBase) { matIds = (e as RoofBase).RoofType.GetMaterialIds(false); }
                                foreach (ElementId matId in matIds)
                                {
                                    if (ceilMatDictionary.TryGetValue(matId, out area))
                                    {
                                        area += e.GetMaterialArea(matId, false);
                                        ceilMatDictionary[matId] = area;
                                    }
                                    else
                                    {
                                        area = e.GetMaterialArea(matId, false);
                                        ceilMatDictionary.Add(matId, area);
                                    }
                                }
                            }
                        }

                        // рисуем ячейки
                        double h1 = 0.0f;
                        int i = 0;
                        foreach (ElementId matId in ceilMatDictionary.Keys)
                        {
                            Material m = doc.GetElement(matId) as Material;
                            string desc = m.get_Parameter(BuiltInParameter.ALL_MODEL_DESCRIPTION).AsString();
                            if (desc.Length == 0) desc = m.Name;
                            TextNote t = DrawTextTop(view, 40, pos + h1, 30, TextAlignFlags.TEF_ALIGN_CENTER, desc);
                            double area = кМетрамКвадратным * ceilMatDictionary[matId];
                            double h3 = GetTextHeight(view, t);
                            DrawTextCenter(view, 70, pos + h1 + h3 / 2, 15, TextAlignFlags.TEF_ALIGN_CENTER, string.Format("{0:0.00}", area));
                            h1 += h3;
                            i++;
                            if (i < ceilMatDictionary.Count)
                                DrawLine(view, 40, pos + h1, 85, pos + h1, lineW1Cat);
                        }

                        double h2 = 0.0f;
                        i = 0;
                        foreach (ElementId matId in wallMatDictionary.Keys)
                        {
                            Material m = doc.GetElement(matId) as Material;
                            string desc = m.get_Parameter(BuiltInParameter.ALL_MODEL_DESCRIPTION).AsString();
                            TextNote t = DrawTextTop(view, 85, pos + h2, 30, TextAlignFlags.TEF_ALIGN_CENTER, desc);
                            double area = кМетрамКвадратным * wallMatDictionary[matId];
                            double h3 = GetTextHeight(view, t);
                            DrawTextCenter(view, 115, pos + h2 + h3 / 2, 15, TextAlignFlags.TEF_ALIGN_CENTER, string.Format("{0:0.00}", area));
                            h2 += h3;
                            i++;
                            if (i < wallMatDictionary.Count)
                                DrawLine(view, 85, pos + h2, 130, pos + h2, lineW1Cat);
                        }
                        double maxH = (h1 > h2) ? h1 : h2;

                        DrawTextCenter(view, 0, pos + maxH / 2, 40, TextAlignFlags.TEF_ALIGN_CENTER, key);
                        pos += maxH;
                        DrawLine(view, 0, pos, 185, pos, lineW1Cat);
                    }

                }

                // рисуем вертикальные границы ведомости
                DrawLine(view, 0, 15, 0, pos, lineW4Cat);
                DrawLine(view, 40, 15, 40, pos, lineW4Cat);
                DrawLine(view, 70, 15, 70, pos, lineW4Cat);
                DrawLine(view, 85, 15, 85, pos, lineW4Cat);
                DrawLine(view, 115, 15, 115, pos, lineW4Cat);
                DrawLine(view, 130, 15, 130, pos, lineW4Cat);
                DrawLine(view, 185, 15, 185, pos, lineW4Cat);

            }
            catch (Exception ee)
            {
                MessageBox.Show(ee.Message);
                транзакция.RollBack();
                return Autodesk.Revit.UI.Result.Failed;
            }
            транзакция.Commit();

            if (view != null)
                revit.Application.ActiveUIDocument.ActiveView = view;

            return Autodesk.Revit.UI.Result.Succeeded;
        }
    }

    [Autodesk.Revit.Attributes.Transaction(Autodesk.Revit.Attributes.TransactionMode.Manual)]
    [Autodesk.Revit.Attributes.Regeneration(Autodesk.Revit.Attributes.RegenerationOption.Manual)]
    [Autodesk.Revit.Attributes.Journaling(Autodesk.Revit.Attributes.JournalingMode.NoCommandData)]
    public class CreateMassFromRoom : IExternalCommand
    {
        private static UIDocument uidoc = null;
        private static Autodesk.Revit.DB.Document doc = null;

        public static string GetRoomLabel(Autodesk.Revit.DB.Architecture.Room room)
        {
            return string.Format("{0} - {1}", room.Number, room.get_Parameter(BuiltInParameter.ROOM_NAME).AsString());
        }

        public static IList<Element> GetRooms()
        {
            IList<Element> allRooms = StartupClass.PlacedRoomFilter(doc);
            Autodesk.Revit.UI.Selection.SelElementSet elementSet = uidoc.Selection.Elements;

            RoomSelectorForm dialog = new RoomSelectorForm();
            dialog.ClearList();
            foreach (Autodesk.Revit.DB.Architecture.Room room in allRooms)
            {
                dialog.AddToList(GetRoomLabel(room), elementSet.Contains(room));
            }

            if (dialog.ShowDialog() == DialogResult.Cancel) return null;

            IList<Element> result = new List<Element>();
            int idx = 0;
            foreach (Autodesk.Revit.DB.Architecture.Room room in allRooms)
            {
                if (dialog.IsChecked(idx)) result.Add(room);
                idx++;
            }
            return result;
        }

        private static FamilySymbol GetFirstSymbol(Family family)
        {
            FamilySymbolSetIterator iter = family.Symbols.ForwardIterator();
            iter.Reset();
            iter.MoveNext();
            return iter.Current as FamilySymbol;
        }

        public String FindSpecialEquipmentTemplate(string familyPath,
                                                          string familyFile,
                                                          string familyMetricFile)
        {
            IEnumerable<string> files = Directory.EnumerateFiles(familyPath, familyFile, SearchOption.AllDirectories);


            String result = files.FirstOrDefault<string>();
            if (!String.IsNullOrEmpty(result))
                return result;


            files = Directory.EnumerateFiles(familyPath, familyMetricFile, SearchOption.AllDirectories);


            return files.FirstOrDefault<string>();
        }

        string familyPath = null;

        private FamilySymbol DoCreateMass(Solid solid, string name)
        {
            FamilySymbol symbol = null;
            Options opt = doc.Application.Create.NewGeometryOptions();
            opt.DetailLevel = doc.ActiveView.DetailLevel;

            string typeName = string.Format("Оболочка помещения {0}", name);
            string familyName = typeName;

            if (familyPath == null)
            {
                // Создаем новую деталь
                familyPath = FindSpecialEquipmentTemplate(doc.Application.FamilyTemplatePath + "\\Концептуальный формообразующий элемент",
                                                                    "Формообразующий элемент.rft",
                                                                    "Метрическая система, формообразующий элемент.rft");

                if (string.IsNullOrEmpty(familyPath))
                {
                    familyPath = FindSpecialEquipmentTemplate(doc.Application.FamilyTemplatePath + "\\Conceptual Mass",
                                                                    "Mass.rft",
                                                                    "Metric Mass.rft");
                    if (string.IsNullOrEmpty(familyPath))
                        throw new Exception("Невозможно найти шаблон семейства 'Метрическая система, формообразующий элемент.rft' в каталоге шаблонов смейст.");
                }
            }

            // Create family
            Document familyDoc = doc.Application.NewFamilyDocument(familyPath);

            // Create freeform element
            using (Transaction t = new Transaction(familyDoc, string.Format("Создание детали {0}", name)))
            {
                t.Start();
                familyDoc.FamilyManager.NewType(typeName);
                Autodesk.Revit.DB.FreeFormElement element = Autodesk.Revit.DB.FreeFormElement.Create(familyDoc, solid);
                t.Commit();
            }


            SaveAsOptions saveOpts = new SaveAsOptions();
            saveOpts.OverwriteExistingFile = true;
            string tempFile = Environment.GetEnvironmentVariable("TEMP") + "\\" + familyName + ".rfa";
            familyDoc.SaveAs(tempFile, saveOpts);
            familyDoc.Close(false);
            // Load family into document
            IFamilyLoadOptions familyLoadOptions = new OverwriteFamilyLoadOptions();


            //(delegate(bool familyInUse, out bool overwrite) { overwrite = true; return true; });
            Transaction ta = new Transaction(doc, string.Format("Загрузка детали {0}", typeName));
            ta.Start();
            Family detailFamily;
            try
            {
                if (doc.LoadFamily(tempFile, familyLoadOptions, out detailFamily))
                {
                    symbol = GetFirstSymbol(detailFamily);
                    Parameter p = symbol.get_Parameter(BuiltInParameter.ALL_MODEL_DESCRIPTION);
                    p.Set("Отделка");
                    ta.Commit();
                }
                else ta.RollBack();
            }
            catch
            {
                ta.RollBack();
                symbol = null;
            }

            return symbol;
        }  

        public Autodesk.Revit.UI.Result Execute(ExternalCommandData revit,
                                               ref string message,
                                               ElementSet elements)
        {
            uidoc = revit.Application.ActiveUIDocument;
            doc = uidoc.Document;

            IList<Element> roomList = GetRooms();
            if (roomList == null) return Autodesk.Revit.UI.Result.Succeeded;

            SpatialElementGeometryCalculator calculator = new SpatialElementGeometryCalculator(doc);
            SpatialElementBoundaryOptions spaOpt = new SpatialElementBoundaryOptions();

            Options opt = new Options();
            opt.ComputeReferences = true;
            opt.IncludeNonVisibleObjects = true;
            string log = "";

            foreach (Autodesk.Revit.DB.Architecture.Room room in roomList)
            {
                Level level = doc.GetElement(room.LevelId) as Level;
                SpatialElementGeometryResults results = calculator.CalculateSpatialElementGeometry((SpatialElement)room);
                Solid geometry = results.GetGeometry();

                FamilySymbol symbol = DoCreateMass(geometry, GetRoomLabel(room));
                if (symbol != null)
                {

                    Transaction ta = new Transaction(doc, string.Format("Размещение формообразующего {0}", GetRoomLabel(room)));
                    ta.Start();
                    XYZ loc = new XYZ();
                    FamilyInstance instance = doc.Create.NewFamilyInstance(loc, symbol, level, StructuralType.NonStructural);
                    ta.Commit();
                }
                else
                {
                    log += GetRoomLabel(room)+"\n";
                }
            }

            if (log.Length > 0)
            {
                log = "Не удалось создать формообразующие для помещений:\n" + log;
                MessageBox.Show(log);
            }

            return Autodesk.Revit.UI.Result.Succeeded;
        }
    }

    [Autodesk.Revit.Attributes.Transaction(Autodesk.Revit.Attributes.TransactionMode.Manual)]
    [Autodesk.Revit.Attributes.Regeneration(Autodesk.Revit.Attributes.RegenerationOption.Manual)]
    [Autodesk.Revit.Attributes.Journaling(Autodesk.Revit.Attributes.JournalingMode.NoCommandData)]
    public class CreateFinishingWall : IExternalCommand
    {
        private static UIDocument uidoc = null;
        private static Autodesk.Revit.DB.Document doc = null;

        private ElementId GetDefaultMaterial()
        {
            FilteredElementCollector collector = new FilteredElementCollector(doc).OfClass(typeof(Material));
            foreach (Material mat in collector)
            {
                if ((string.Compare(mat.Name, "По умолчанию") == 0) || (string.Compare(mat.Name, "By Default") == 0)) return mat.Id;
            }
            return collector.First().Id;
        }

        private void GetWallTypes(out WallType singleType, out WallType doubleType)
        {
            singleType = new FilteredElementCollector(doc).OfClass(typeof(WallType)).Cast<WallType>().FirstOrDefault(q => q.Name == "Basic finishing");
            doubleType = new FilteredElementCollector(doc).OfClass(typeof(WallType)).Cast<WallType>().FirstOrDefault(q => q.Name == "Basic finishing 2x");

            if (singleType == null)
            {
                ElementId matId = GetDefaultMaterial();
                FilteredElementCollector collector = new FilteredElementCollector(doc).OfClass(typeof(Wall));
                foreach (Wall wall in collector)
                {
                    WallType aWallType = wall.WallType;
                    if (WallKind.Basic == wall.WallType.Kind)
                    {
                        using (Transaction ta = new Transaction(doc, "Создание типа стены Basic finishing"))
                        {
                            ta.Start();
                            singleType = aWallType.Duplicate("Basic finishing") as WallType;
                            double thikness = UnitUtils.Convert(20.0, DisplayUnitType.DUT_MILLIMETERS, DisplayUnitType.DUT_FEET_FRACTIONAL_INCHES);
                            CompoundStructure wallCS = CompoundStructure.CreateSingleLayerCompoundStructure(MaterialFunctionAssignment.Finish1, thikness, matId);
                            singleType.SetCompoundStructure(wallCS);
                            singleType.get_Parameter(BuiltInParameter.ALL_MODEL_MODEL).Set("Отделка");
                            ta.Commit();
                        }
                        break;
                    }
                }
            }

            if (doubleType == null)
            {
                ElementId matId = GetDefaultMaterial();
                FilteredElementCollector collector = new FilteredElementCollector(doc).OfClass(typeof(Wall));
                foreach (Wall wall in collector)
                {
                    WallType aWallType = wall.WallType;
                    if (WallKind.Basic == wall.WallType.Kind)
                    {
                        using (Transaction ta = new Transaction(doc, "Создание типа стены Basic finishing 2x"))
                        {
                            ta.Start();
                            doubleType = aWallType.Duplicate("Basic finishing 2x") as WallType;
                            double thikness = UnitUtils.Convert(40.0, DisplayUnitType.DUT_MILLIMETERS, DisplayUnitType.DUT_FEET_FRACTIONAL_INCHES);
                            CompoundStructure wallCS = CompoundStructure.CreateSingleLayerCompoundStructure(MaterialFunctionAssignment.Finish1, thikness, matId);
                            doubleType.SetCompoundStructure(wallCS);
                            doubleType.get_Parameter(BuiltInParameter.ALL_MODEL_MODEL).Set("Отделка");
                            ta.Commit();
                        }
                        break;
                    }
                }
            }
        }

        public Autodesk.Revit.UI.Result Execute(ExternalCommandData revit,
                                               ref string message,
                                               ElementSet elements)
        {
            uidoc = revit.Application.ActiveUIDocument;
            doc = uidoc.Document;

            IList<Element> roomList = CreateMassFromRoom.GetRooms();
            if (roomList == null) return Autodesk.Revit.UI.Result.Succeeded;

            WallType wType;
            WallType wType2x;
            GetWallTypes(out wType, out wType2x);

            List<Wall> walls = new List<Wall>();
            OverlapWarningSwallower failMaster = new OverlapWarningSwallower();
            SpatialElementGeometryCalculator calculator = new SpatialElementGeometryCalculator(doc);

            string log = "";

            foreach (Autodesk.Revit.DB.Architecture.Room room in roomList)
            {
                SpatialElementGeometryResults results = calculator.CalculateSpatialElementGeometry((SpatialElement)room);
                Solid solid = results.GetGeometry();
                if (solid.Faces.Size > 0)
                {
                    string roomNumber = room.Number;
                    string roomName = room.get_Parameter(BuiltInParameter.ROOM_NAME).AsString();

                    Transaction ta = new Transaction(doc, "Размещение стены-отделки");
                    FailureHandlingOptions failOpt = ta.GetFailureHandlingOptions();

                    failOpt.SetFailuresPreprocessor(failMaster);
                    ta.SetFailureHandlingOptions(failOpt);
                    failMaster.Neightbours.Clear();

                    ta.Start();

                    try
                    {
                        foreach (Face f in solid.Faces)
                        {
                            XYZ n = f.ComputeNormal(new UV(0.5, 0.5));
                            if (Math.Abs(n.Z) > 0.0001) continue;

                            List<Curve> curveArray = new List<Curve>();
                            foreach (EdgeArray edges in f.EdgeLoops)
                            {
                                foreach (Edge edge in edges)
                                {
                                    curveArray.Add(edge.AsCurve());
                                }
                                break;
                            }

                            if (curveArray.Count > 0)
                            {
                                Wall createdWall = Wall.Create(doc, curveArray, wType2x.Id, room.LevelId, false, n.Negate());
                                if (createdWall != null)
                                {
                                    walls.Add(createdWall);
                                    createdWall.get_Parameter(BuiltInParameter.WALL_BASE_OFFSET).Set(0);
                                    createdWall.get_Parameter(BuiltInParameter.WALL_TOP_OFFSET).Set(0);
                                    createdWall.get_Parameter(BuiltInParameter.WALL_KEY_REF_PARAM).Set(2);
                                    Parameter p = StartupClass.GetOrCreateParameter(revit.Application, createdWall, "Номер помещения", ParameterType.Text, true);
                                    p.Set(roomNumber);
                                    p = StartupClass.GetOrCreateParameter(revit.Application, createdWall, "Имя помещения", ParameterType.Text, true);
                                    p.Set(roomName);
                                    StartupClass.GetOrCreateParameter(revit.Application, createdWall, "Перечень помещений", ParameterType.Text, true);
                                }
                            }
                        }
                        ta.Commit();

                        if (walls.Count > 0)
                        {
                            ta.Start();
                            foreach (Wall wall in walls)
                            {
                                wall.WallType = wType;
                            }
                            ta.Commit();
                            ta.Start();
                            foreach (Wall wall in walls)
                            {
                                wall.get_Parameter(BuiltInParameter.WALL_KEY_REF_PARAM).Set(3);
                            }
                            ta.Commit();

                            ta.Start();
                            if (failMaster.Neightbours.Count > 0)
                            {
                                foreach (ICollection<ElementId> coll in failMaster.Neightbours)
                                {
                                    if (coll.Count > 1)
                                    {
                                        Element e1 = doc.GetElement(coll.First());
                                        Element e2 = doc.GetElement(coll.Last());
                                        JoinGeometryUtils.JoinGeometry(doc, e1, e2);
                                    }
                                }
                            }
                            ta.Commit();
                        }
                    }
                    catch
                    {
                        log += CreateMassFromRoom.GetRoomLabel(room) + "\n";
                    }
                }
            }

            if (log.Length > 0)
            {
                log = "Не удалось создать стены в помещениях:\n" + log;
                MessageBox.Show(log);
            }

            return Autodesk.Revit.UI.Result.Succeeded;
        }
    }

    [Autodesk.Revit.Attributes.Transaction(Autodesk.Revit.Attributes.TransactionMode.Manual)]
    [Autodesk.Revit.Attributes.Regeneration(Autodesk.Revit.Attributes.RegenerationOption.Manual)]
    [Autodesk.Revit.Attributes.Journaling(Autodesk.Revit.Attributes.JournalingMode.NoCommandData)]
    public class ShowFinishingOptions : IExternalCommand
    {
        public Autodesk.Revit.UI.Result Execute(ExternalCommandData revit,
                                               ref string message,
                                               ElementSet elements)
        {
            FinishingOptionsForm form = new FinishingOptionsForm();
            form.TakeoffGroupingByMaterial = StartupClass.groupingByMaterial;
            form.TakeoffFirstColumnValue = StartupClass.firstColumnValue;
            form.TakeoffLanguage = StartupClass.takeoffLanguage;

            if (form.ShowDialog() == DialogResult.OK)
            {
                StartupClass.groupingByMaterial = form.TakeoffGroupingByMaterial;
                StartupClass.firstColumnValue = form.TakeoffFirstColumnValue;
                StartupClass.takeoffLanguage = form.TakeoffLanguage;
            }

            return Autodesk.Revit.UI.Result.Succeeded;
        }
    }
}
